#!/usr/bin/python -S
# -*- coding: utf8 -*-

########################################################
# Part of MILIM NIRDAFOT project.
# Written by H. Magal, 2014
# See details on the project at the site:
#   https://sites.google.com/site/nirdafotdownloads/home
########################################################

######################################################
# to use the debug mode (in which files are downloaded
# from a site which is inaccessible to users), set
# next var to True
debugMode = False
######################################################

import sys
# on Windows, this module is called twice (multiprocessing), and the other time
# with the name as it appears on next line:
if (__name__ == '__main__') or (__name__ == '__parents_main__'):
    sys.setdefaultencoding("utf-8")         # this is needed for the windows.
    #####################################################################################
    __version__ = '1.02'                    # UPDATE VERSION UPON ANY CHANGE OF THIS FILE
    #####################################################################################
    import site                             # we call python with -S, so now we import the site module
else:
    import shutil
    import zipfile
    import constants

import os
import httplib
import tarfile
import xml.etree.ElementTree as XMLET
import multiprocessing
import time

wxIsFound = None
try:
    import wx
    wxIsFound = True
except:    # catch all errors. It might be that wxPython was not installed
    wxIsFound = False


class handleGoggleSiteFiles ():
    def __init__ (self):
        # these definitions are where SW updates are held
        self.SWsiteName = 'sites.google.com'
        self.SWsiteFolder = '/site/nirdafotdownloads/home/download/'
        if debugMode is True:
            self.SWsiteFolder = self.SWsiteFolder.replace ('home', 'debug')


    # This function is done on a subprocess !!!
    # It returns the data read from the site (the file content) through a PIPE.
    # If writeFile is True, then the data is written (binary mode!) to local file
    # with the name of filename. In that case the value True is sent to the PIPE !
    # on any error it sends None to the PIPE.
    # The main process terminates this subprocess on TIMEOUT event !
    def get_URL_file_subprocess (self, filename, writeFile, xmitpipe):
        conn = httplib.HTTPSConnection (self.SWsiteName)
        conn.request ("GET", self.SWsiteFolder + filename)
        while True:
            r1 = conn.getresponse()
            if r1.status == 200:                            # This is the OK code !
                data1 = r1.read ()
                conn.close ()
                if writeFile is True:
                    tarout = open (filename, 'wb')
                    tarout.write (data1)
                    tarout.close ()
                    xmitpipe.send (True)
                else:
                    xmitpipe.send (data1)
                xmitpipe.close ()
                return
            elif r1.status == 302:                          # This is the temporary redirection code !
                head1 = r1.getheader ('location')
                conn.close ()
                httpss = 'https://'
                if not head1.startswith (httpss):
                    xmitpipe.send (None)
                    xmitpipe.close ()
                    return
                head2 = head1.replace (httpss, '', 1)      # remove leading 'https://'
                part1 = head2.partition ('/')              # partition around the first '/'
                conn = httplib.HTTPSConnection (part1[0])
                conn.request ("GET", '/' + part1[2])       # the rest of folder/file part
            else:                                          # this is an unknown status code
                conn.close ()
                xmitpipe.send (None)
                xmitpipe.close ()
                return


    # This function returns the data read from the site (the file content).
    # If writeFile is True, then the data is written (binary mode!) to local file
    # with the name of filename. In that case the returned value is True !
    # on any error it returns None
    def get_URL_file (self, filename, writeFile=False):
        rend, xend = multiprocessing.Pipe (duplex=False)            # create a UniDirectional pipe
        proc = multiprocessing.Process (target=self.get_URL_file_subprocess, args=(filename, writeFile, xend))
        proc.start ()
        seconds = 0
        while (seconds < 40):                               # 40 seconds timeout to finish the subprocess
            time.sleep (1.0)                                # sleep 1 second
            seconds += 1
            rc = rend.poll ()                               # poll pipe state
            if rc is True:
                proc.join ()                                # wait for subprocess to end
                res = rend.recv ()                          # read pipe
                xend.close ()
                rend.close ()
                return res
        proc.terminate ()                                   # the loop exited with timeout
        xend.close ()
        rend.close ()
        return None


    # return a tuple of tuples. Returns None on any error reading the file from the net.
    # 1st tuples is (engine, database) versions, 2nd tuple is (engineTar, engineZip, database) files sizes.
    def parse_URL_file (self, filename):
        data1 = self.get_URL_file (filename, writeFile=False)
        if data1 is None:
            return None
        root = XMLET.XML (data1)
        versions = root.find ('versions')
        vengine   = versions.find ('engine')
        vdatabase = versions.find ('database')
        sizes     = root.find ('filesize')
        enginetar = sizes.find ('enginetar')
        enginezip = sizes.find ('enginezip')
        database  = sizes.find ('database')
        return ((vengine, vdatabase), (enginetar, enginezip, database))

    # convert version of the form xx.yyy to a pure integer by mult the fraction by 1000
    # returns the integer
    def ver_multiplied_by_1000 (self, vtext):
        t3 = vtext.partition ('.')
        if t3[1] != '.':
            return 0
        if len (t3[2]) == 1:                        # if the version is x.y
            lsv = int (t3[2]) * 100
        elif len (t3[2]) == 2:                      # if the version is x.yy
            lsv = int (t3[2]) * 10
        else:
            lsv = int (t3[2])                       #    the version is x.yyy
        return (int (t3[0]) * 1000) + lsv


    # returns True/false for success/failure
    def fetch_tar_and_extract (self, tarname):
        rc = self.get_URL_file (tarname, writeFile=True)
        if not rc is True:
            return False
        try:
            tarin = tarfile.open (name=tarname, mode='r:gz')  # open nirdafotDB.X.Y.tar.gz file
            tarin.list (verbose=False)              # short list format
            tarin.extractall ()                     # if list didn't raise an exception, extract all files
        except:
            os.remove (tarname)
            return False

        tarin.close ()
        os.remove (tarname)
        return True


    # returns True/false for success/failure
    def update_database (self, version):
        # the name 'nirdafotDB.' is a notation known to the DataBase module too, so dont change it !
        tarname = 'nirdafotDB.' + version + '.tar.gz'
        return self.fetch_tar_and_extract (tarname)



    # returns True/false for success/failure
    def update_engine (self, engine_version, zip_size, parentFrame):
        # the name 'nirdafotEngine.' is a notation known to the SW upgrade module
        # (create_tar_file.py), so dont change it !
        basename = 'nirdafotEngine.' + engine_version
        # on Windows, this module is called twice (multiprocessing), and the other time
        # with the name as it appears on next line:
        if (__name__ != '__main__') and (__name__ != '__parents_main__'):
            # testing SW update under windows/1folder
            if (constants.platform_is_Win is True) and (constants.Win_uses_1folder is True):
                dlg = wx.ProgressDialog ("Downloading file", "Downloaded 0% of " + zip_size,
                                         maximum = 100, parent = parentFrame, style = wx.PD_AUTO_HIDE)
                count = 0
                wx.MilliSleep (5)           # I found it neccessary between dialog creation and first update
                try:
                    os.mkdir ('splits')
                except:
                    pass                    # if gives error of existing fodler, ignore it

                for i in xrange (constants.splitparts):             # read the XX parts of the zip file and concatenate them into a single zip
                    dlg.Update (count, "Downloaded " + str(count) + "% of " + zip_size)
                    rc = self.get_URL_file ('splits/' + basename + constants.win_splits_extension + str(i), writeFile=True)
                    if rc is None:                                  # it means some error had occured
                        return False
                    count += 11
                dlg.Update (100, "Downloaded 100% of " + zip_size)

                # read all splits and write into a singel ZIP file
                zipname = basename + '.zip'
                zipout  = open (zipname, 'wb')
                for i in xrange (constants.splitparts):             # read the XX parts of the zip file and concatenate them into a single zip
                    sfile = open ('splits/' + basename + constants.win_splits_extension + str(i), mode='rb')
                    data1 = sfile.read ()
                    sfile.close ()
                    zipout.write (data1)
                zipout.close ()
                shutil.rmtree ('splits')
                zipfil = zipfile.ZipFile (file=zipname, mode='r')
                rc = zipfil.testzip ()                              # test integrity of zip file
                dlg.Destroy ()
                if rc is None:                                      # this is the success indication
                    tfold = '.\\temp'
                    try:
                        shutil.rmtree (tfold)                       # if folder exists.just delete it
                    except:
                        pass                                        # if not, just ignore it
                    os.mkdir (tfold)                                # create temp folder under current working directory
                    zipfil.extractall (tfold)                       # extract all to temp folder under current working directory
                    zipfil.close ()
                    os.remove (zipname)
                    return True
                zipfil.close ()
                os.remove (zipname)
                return False

        # we comes here in 'main' mode and in 'Pyhton mode' (both Win & Linux)
        tarname = basename + '.tar.gz'
        return self.fetch_tar_and_extract (tarname)


# this function is called when this file is called by its own at first boot of program
def main ():
    if not wxIsFound is True:
        print 'Error: wxPython is not installed. Install it before program installation !'
        return
    print '     >>>> Reading files from the Internet. Please wait ...'
    site = handleGoggleSiteFiles ()
    site_versions_size = site.parse_URL_file ('nirdafot.xml')
    if site_versions_size is None:                       # This happens on any error or timeout
        print '     >>>> ERROR >>>> Internet access failed. Quit setup ...'
        return
    site_versions = site_versions_size [0]
    print '     >>>> Downloading DataBase:', site_versions[1].text, ' Engine:', site_versions[0].text
    rc = site.update_database (site_versions[1].text)   # we need the new version to form the proper file name
    if rc is True:
        print '     >>>> DataBase was Successfully built'
    else:
        print '     >>>> ERROR >>>> DataBase build failed'
    rc = site.update_engine (site_versions[0].text, None, None) # we need the new version to form the proper file name
    if rc is True:
        print '     >>>> Program was Successfully built'
    else:
        print '     >>>> ERROR >>>> Program build failed'


# A custom dialog box to check the system right to left support
class MyCustomDialog (wx.Dialog):
    def __init__ (self, size, message, topText, botText, topVal, botVal, **kw):
        super (MyCustomDialog, self).__init__ (**kw)
        self.SetSize (size)
        vbox = wx.BoxSizer (wx.VERTICAL)
        rmsg = message[::-1]                            # this operation REVERSES the string
        msg = message + '\n' + rmsg
        st = wx.StaticText (self, label=msg)

        tButton = wx.Button (self, label=topText)
        bButton = wx.Button (self, label=botText)

        vbox.Add (st,      proportion=1, flag=wx.ALL|wx.ALIGN_CENTER, border=5)
        vbox.Add (tButton, proportion=1, flag=wx.ALL|wx.ALIGN_CENTER, border=5)
        vbox.Add (bButton, proportion=1, flag=wx.ALL|wx.ALIGN_CENTER, border=5)
        self.SetSizer (vbox)

        tButton.Bind (wx.EVT_BUTTON, self.OnTopBut)
        bButton.Bind (wx.EVT_BUTTON, self.OnBotBut)
        self.Bind    (wx.EVT_CLOSE,  self.OnBotBut)         # I treat close as being unsupported system
        self.tVal = topVal
        self.bVal = botVal
        self.Centre ()

    def OnTopBut (self, e):
        self.EndModal (self.tVal)           # this return the top value to caller
        self.Destroy ()

    def OnBotBut (self, e):
        self.EndModal (self.bVal)           # this return the bottom value to caller
        self.Destroy ()



# A custom dialog box to check the system right to left support
class showCustomDialog (wx.Frame):
    def __init__ (self, *args, **kw):
        super (showCustomDialog, self).__init__(*args, **kw)
        self.SetSize ((10, 10))
        self.SetTitle ('')
        buttonMsg = unicode ("לחץ כאן אם אתה קורא מימין לשמאל", 'utf-8')
        rtl_support = unicode ('בדיקת תמיכת מערכת בעברית ימין-שמאל', 'utf-8')
        rmsg = buttonMsg [::-1]                    # this operation REVERSES the string
        custom = MyCustomDialog ((400,200), rtl_support, buttonMsg, rmsg,
                                 1, 0, parent=None, title='Checking Right to Left support')
        custom.ShowModal()
        rc = custom.GetReturnCode ()
        self.Destroy ()
        if not rc == 1:
            emsg = 'Your PC does not support Hebrew Right to Left writing.\nPlease fix it and try again.'
            wx.MessageBox (emsg, 'Error-No Right to left support', wx.OK | wx.ICON_ERROR)
            sys.exit (1)


if __name__ == '__main__':
    #######################################################################################
    # this line is a must due to Python API docs.
    # see the page
    #  http://docs.python.org/2/library/multiprocessing.html#multiprocessing.freeze_support
    # It must be the first line after if __name__ == '__main__'
    multiprocessing.freeze_support()
    #######################################################################################

    # create a custom dialog to check RTL support. If the wrong button is pressed by the user, the program terminates.
    print '     >>>> setup version:', __version__
    ex = wx.App ()
    showCustomDialog (None)         # A custom dialog box to check the system right to left support
    ex.MainLoop ()
    main ()                         # download the 2 files engine+dataBase tar.gz